package engine;
import java.util.*;

import quadTree.*;


public class Scene {
	
	/*
	 * @ box!=null;
	 * @ objects!=null;
	 * @ forces!=null;
	 */
	private BoundingBox box;
	private PhysicsObject[] objects;
	private List<Force> localForces;
	private List<Force> globalForces;
	private QuadTree quadTree;
	private float degradation;
	
	/**
	 * Scene Constructor
	 * @param maxSize the maximum size of the simulation - in X,Y terms
	 */
	public Scene(float maxSize) {
		if(maxSize<=0){System.out.println("Illegal Input, cannot have negative or zero size scene");return;}
		box = new BoundingBox(new Vector(0, 0), new Vector(maxSize, maxSize));
		quadTree = new QuadTree(box, 0);
		objects = new PhysicsObject[5];
		localForces = new ArrayList<Force>();
		globalForces = new ArrayList<Force>();
		degradation = 0.99f;
	}
	/**
	 * Scene Constructor
	 * @param box the BoundingBox describing the scene's maximum X and Y
	 */
	public Scene(BoundingBox box) {
		if(box==null){System.out.println("Illegal Input, cannot have null BoundingBox");return;}
		this.box = box;
		quadTree = new QuadTree(this.box, 4);
		objects = new PhysicsObject[5];
		localForces = new ArrayList<Force>();
		globalForces = new ArrayList<Force>();
	}
	
	public String toString() {
		String ret = "";
		ret += "Degradation: \n";
		ret += degradation + "\n";
		ret += "Bounding Box: \n";
		ret += box + "\n";
		ret += "Physics Objects: \n";
		ret += objects + "\n";
		ret += "Forces: \n";
		ret += localForces + "\n";
		ret += globalForces + "\n";
		return ret;
	}
	/**
	 * getDegradation returns the rate at which energy is "lost" in this scene
	 * @return the degradation rate
	 */
	/*
	 * @requires this!=null;
	 * @returns float;
	 */
	public float getDegradation() {
		return degradation;
	}
	/**
	 * setDegradation sets the rate at which energy is "lost" in the scene 
	 * @param degradation the rate of degradation
	 */
	/*
	 * @returns \nothing;
	 */
	public void setDegradation(float degradation) {
		this.degradation = degradation;
	}

	/**
	 * getQuadTree returns the quad tree used by the scene
	 * @return the quad Tree
	 */
	/*
	 * @ requires quadTree!=null;
	 * @ assignable \nothing;
	 * @ returns this.quadTree;
	 */
	public QuadTree getQuadTree() {
		return quadTree;
	}
	
	/**
	 * addObject adds a PhysicsObject to the scene
	 * @param p the PhysicsObject to add
	 */
	/*
	 * @ requires this !=null;
	 * @ ensures objects.getObject(p)==p;
	 * @ returns \nothing;
	 */
	public void addObject(PhysicsObject p)
	{
		if(p==null){System.out.println("Illegal Input, cannot have null PhysicsObject");return;}
		objects[p.getIndex()] = p;
		quadTree.insert(p);
		if(p.getIndex() == objects.length-1) {
			PhysicsObject[] temp = new PhysicsObject[objects.length * 2];
			for(int i = 0; i < objects.length; i++) {
				temp[i] = objects[i];
			}
			objects = temp;
		}
	}
	/**
	 * removeObject removes the specified PhysicsObject from the scene
	 * @param p the physicsObject to remove
	 */
	/*
	 * @ requires this !=null;
	 * @ ensures objects.getObject(p)!=p;
	 * @ returns \nothing;
	 */
	public void removeObject(PhysicsObject p)
	{
		if(p==null){System.out.println("Illegal Input, cannot have null PhysicsObject");return;}
		objects[p.getIndex()] = null;
		for(QuadNode q : quadTree.findIntersect(p.getBoundingBox())) {
			if(q.getData().contains(p)) {
				q.getData().remove(p);
				break;
			}
		}
	}
	/**
	 * addForce adds a force to the scene
	 * @param f the force to add
	 */
	/*
	 * @ requires this !=null;
	 * @ ensures forces.getForce(f)==f;
	 * @ returns \nothing;
	 */
	public void addForce(Force f)
	{
		if(f==null){System.out.println("Illegal Input, cannot have null force");return;}
		if(f.getRange()==Float.POSITIVE_INFINITY)
			globalForces.add(f);
		else
			localForces.add(f);
	}
	/**
	 * removeForce removes the passed force from the scene
	 * @param f the force to remove
	 */
	/*
	 * @ requires this !=null;
	 * @ ensures forces.getForce(f)!=f;
	 * @ assignable \nothing
	 * @ returns \nothing;
	 */
	public void removeForce(Force f)
	{
		if(f==null){System.out.println("Illegal Input, cannot have null force");return;}
		if(f.getRange()==Float.POSITIVE_INFINITY)
			globalForces.remove(f);
		else
			localForces.remove(f);
	}
	/**
	 * changeForceRange changes the properties of a force and places it in the correct force list
	 * @param f the force to be changed
	 * @param r the range of the force
	 */
	/*
	 * @ returns \nothing;
	 * @ assignable f.range;
	 * @ ensures f.range==Float.POSITIVE_INFINITY ==> globalForces.contains(f);
	 * @ ensures f.range!=Float.POSITIVE_INFINITY ==> localForces.contains(f);
	 */
	public void changeForceRange(Force f, float r)
	{
		if(f==null){System.out.println("Illegal Input, cannot have null force");return;}
		if(r==Float.POSITIVE_INFINITY && f.getRange()!=Float.POSITIVE_INFINITY)
		{
			localForces.remove(f);
			globalForces.add(f);
		}
		else if( r!=Float.POSITIVE_INFINITY && f.getRange()==Float.POSITIVE_INFINITY )
		{
			globalForces.remove(f);
			localForces.add(f);
		}
		
		f.setRange(r);
	}
	/**
	 * update updates the scene using an elapsed time argument
	 * @param t the time elapsed
	 */
	/*
	 * @ requires this !=null;
	 * @ assignable \nothing;
	 * @ returns \nothing;
	 */
	public String update(float t, boolean writeToLog)
	{
		
		Vector netForce = calcNetForce();
		if (writeToLog) {
			String ret = "";
			for(PhysicsObject p : objects)//how do forces fit in here...
			{//method for calculating global forces, 
				if(p != null) {
					ret += p.update(t, degradation, netForce, writeToLog);
				}
			}
			ret += applyLocals(t,writeToLog);
			return ret;
		}
		else {
			for(PhysicsObject p : objects)//how do forces fit in here...
			{//method for calculating global forces, 
				if(p != null) {
					p.update(t, degradation, netForce);
				}
			}
			applyLocals(t);
			return null;
		}
	}
	/**
	 * applyLocals applies the local forces acting on all objects in the scene
	 */
	/*
	 * not sure how to express the assignability of all the objects in objects' force
	 * @returns \nothing
	 */
	public String applyLocals(float t,boolean log)
	{
		if(!log) {
			applyLocals(t);
			return null;
		}
		String ret = "";
		for(Force f : localForces)
		{
			BoundingBox fBox = f.getForceBox();
			for(QuadNode q : quadTree.findIntersect(fBox))
			{
				for(PhysicsObject p : q.getData())
				{
					if(p.getCenterOfMass().dist(f.getPos()) <= f.getRange()) {
						ret += p.applyForce(f,t);
					}
				}
			}
		}
		return ret;
		
	}
	
	/**
	 * applyLocals applies the local forces acting on all objects in the scene
	 */
	/*
	 * not sure how to express the assignability of all the objects in objects' force
	 * @returns \nothing
	 */
	public void applyLocals(float t)
	{
		for(Force f : localForces)
		{
			BoundingBox fBox = f.getForceBox();
			for(QuadNode q : quadTree.findIntersect(fBox))
			{
				
				for(PhysicsObject p : q.getData())
				{
					if(p.getCenterOfMass().dist(f.getPos()) <= f.getRange()) {
						p.applyForce(f,t);
					}
				}
			}
		}
		
	}
	/**
	 * Calculates the net force of all the global forces in the scene
	 * @return the sum of all the scene's global forces
	 */
	/*
	 * @ assignable net;
	 * @ ensures (\result).instanceof(Vector) ;
	 */
	public Vector calcNetForce()
	{
		Vector net = new Vector(0,0);
		
		for(Force f : globalForces)
		{
			net.add(f.getDir());	
		}
		return net; 	
	}
	/**
	 * getObjectList returns the list of objects currently in the scene
	 * @return the scene's list of objects
	 */
	/*
	 * @ requires this!=null;
	 * @ assignable \nothing
	 * @ returns objects;
	 */
	public PhysicsObject[] getObjectList()
	{
		return objects;
	}
	/**
	 * getObject gets the force in objects at a given index
	 * @param index the index of the PhysicsObject in question
	 * @return the PhysicsObject in objects at the index
	 */
	/*
	 * @ requires this!=null;//Apply the net global Force
			v.setVelo(Vector.add(v.getVelo(),netForce));
			//Still need to update based on time
			v.getVelo().scl(t);
			v.setPos(Vector.add(v.getPos(),v.getVelo()));
	 * @ assignable \nothing;
	 * @ returns objects.get(index);
	 */
	public PhysicsObject getObject(int index)
	{
		if(index<0||index>objects.length-1){System.out.println("Illegal Input, index invalid");return null;}
		return objects[index];
	}
	/**
	 * getGlobalForce gets the force in the globalForces List at a given index
	 * @param index the index of the force in question
	 * @return the force in the gloablForces List at the index
	 */
	/*
	 * @ requires this!=null;
	 * @ assignable \nothing;
	 * @ returns objects.get(index);
	 */
	public Force getGlobalForce(int index)
	{
		if(index<0||index>globalForces.size()-1){System.out.println("Illegal Input, index invalid");return null;}
		if(globalForces.isEmpty())return null;
		return globalForces.get(index);
	}
	/**
	 * getLocalForce gets the force in the localForces List at a given index
	 * @param index the index of the force in question
	 * @return the force in the localForces List at the index
	 */
	/*
	 * @ requires this!=null;
	 * @ assignable \nothing;
	 * @ returns objects.get(index);
	 */
	public Force getLocalForce(int index)
	{
		if(index<0||index>localForces.size()-1){System.out.println("Illegal Input, index invalid");return null;}
		if(localForces.isEmpty())return null;
		return localForces.get(index);
	}
}
